package com.smedi.ismedi.authoritymanagement.core.application.query.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.smedi.ismedi.authority.starter.response.RoleResponse;
import com.smedi.ismedi.authority.starter.util.AuthorityUtil;
import com.smedi.ismedi.authority.starter.util.VerifyHelper;
import com.smedi.ismedi.authoritymanagement.core.application.command.ElementListQuery;
import com.smedi.ismedi.authoritymanagement.core.application.command.ElementOneQuery;
import com.smedi.ismedi.authoritymanagement.core.application.command.ElementPageQuery;
import com.smedi.ismedi.authoritymanagement.core.application.command.ElementTreeCommand;
import com.smedi.ismedi.authoritymanagement.core.application.convert.AppConverter;
import com.smedi.ismedi.authoritymanagement.core.application.query.ElementQueryService;
import com.smedi.ismedi.authoritymanagement.core.domain.aggregatemodel.Element;
import com.smedi.ismedi.authoritymanagement.core.domain.aggregatemodel.ElementNode;
import com.smedi.ismedi.authoritymanagement.core.domain.dto.ElementQuery;
import com.smedi.ismedi.authoritymanagement.core.domain.repository.ElementRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.smedi.ismedi.web.starter.thread.ThreadPool.IO_INSTANCE;

/**
 * 元素查询 实现类
 *
 * @author xienannan
 * @date 2021/08/28
 */
@Service
@AllArgsConstructor
public class ElementQueryServiceImpl implements ElementQueryService {

    private final ElementRepository elementRepository;
    private final AppConverter appConverter;
    private final AuthorityUtil authorityUtil;

    @Override
    public Element getElementOne(ElementOneQuery elementOneQuery) {
        ElementQuery elementQuery = appConverter.elementOneQueryToElementQuery(elementOneQuery);
        return elementRepository.getElementOne(elementQuery);
    }

    @Override
    public List<Element> getElementList(ElementListQuery elementListQuery) {
        ElementQuery elementQuery = appConverter.elementListQueryToElementQuery(elementListQuery);
        return elementRepository.getElementList(elementQuery);
    }

    @Override
    public Page<Element> getElementPage(ElementPageQuery elementPageQuery) {
        ElementQuery elementQuery = appConverter.elementPageQueryToElementQuery(elementPageQuery);
        return elementRepository.getElementPage(elementQuery);
    }

    @Override
    public List<ElementNode> getTree(ElementTreeCommand elementTreeCommand) {
        boolean byAuthenticationFilter = null != elementTreeCommand.getByAuthenticationFilter() && elementTreeCommand.getByAuthenticationFilter();
        List<ElementNode> elementNodes = new ArrayList<>();
        List<ElementNode> nodesWithChildren = new ArrayList<>();

        AtomicReference<List<Element>> atomicElements = new AtomicReference<>(new ArrayList<>());
        AtomicReference<List<Element>> atomicElementInitials = new AtomicReference<>(new ArrayList<>());

        Set<Long> roleIds = new HashSet<>();
        if (null != elementTreeCommand.getRoleId()) {
            roleIds.add(elementTreeCommand.getRoleId());
        }
        if (byAuthenticationFilter) {
            List<RoleResponse> roles = authorityUtil.getLoginAccess().getRoles();
            roleIds.addAll(roles.stream().map(RoleResponse::getRoleId).collect(Collectors.toList()));
        }

        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(roleIds) || !byAuthenticationFilter) {
                        atomicElements.set(elementRepository.getElementList(ElementQuery.builder().roleIds(new ArrayList<>(roleIds)).build()));
                    }
                }, IO_INSTANCE),
                CompletableFuture.runAsync(() -> atomicElementInitials.set(elementRepository.getElementList(ElementQuery.builder().initial(true).build())), IO_INSTANCE)
        ).join();

        Set<Element> elements = new HashSet<>();
        if (VerifyHelper.isAvailable(atomicElements.get())) {
            elements.addAll(atomicElements.get());
        }
        if (VerifyHelper.isAvailable(atomicElementInitials.get())) {
            elements.addAll(atomicElementInitials.get());
        }

        Map<Long, ElementNode> idNodeMap = new HashMap<>();
        Map<Long, List<Long>> idChildrenIdsMap = new HashMap<>();
        for (Element element : elements) {
            ElementNode elementNode = appConverter.elementToNode(element);
            if (null == element.getParentId()) {
                elementNodes.add(elementNode);
            }
            if (null != element.getParentId()) {
                idNodeMap.put(elementNode.getId(), elementNode);
                List<Long> childrenIds = idChildrenIdsMap.getOrDefault(element.getParentId(), new ArrayList<>());
                childrenIds.add(element.getId());
                idChildrenIdsMap.put(element.getParentId(), childrenIds);
            }
        }

        for (ElementNode elementNode : elementNodes) {
            nodesWithChildren.add(this.findChildren(elementNode, idNodeMap, idChildrenIdsMap));
        }

        return nodesWithChildren.stream().sorted(Comparator.comparing(ElementNode::getDisplayOrder)).collect(Collectors.toList());
    }

    ElementNode findChildren(ElementNode elementNode, Map<Long, ElementNode> idNodeMap, Map<Long, List<Long>> idChildrenIdsMap) {
        List<ElementNode> children = new ArrayList<>();
        List<Long> childrenIds = idChildrenIdsMap.getOrDefault(elementNode.getId(), new ArrayList<>());
        for (Long id : childrenIds) {
            ElementNode child = idNodeMap.get(id);
            findChildren(child, idNodeMap, idChildrenIdsMap);
            children.add(child);
        }
        elementNode.setChildren(children.stream().sorted(Comparator.comparing(ElementNode::getDisplayOrder)).collect(Collectors.toList()));

        return elementNode;
    }
}
